package etcd

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"os/signal"
	"time"
	"os"
	"syscall"

	clientv3 "go.etcd.io/etcd/client/v3"
)

// Registry for grpc server
type Registry struct {
	endpoints   []string
	key         string
	fullKey     string
	id          int64
	value       string
	closeCh     chan struct{}
	leasesID    clientv3.LeaseID
	keepAliveCh <-chan *clientv3.LeaseKeepAliveResponse
	ttl         int64
	cli         *clientv3.Client
}

// NewRegister create a register base on etcd
func NewRegistry(endpoints []string, key, value string, ) *Registry {
	return &Registry{
		endpoints: endpoints,
		key:       key,
		value:     value,
	}
}

func (r *Registry) KeepAlive(ttl int64) (*Registry, error) {
	var err error

	if r.cli, err = DialClient(r.endpoints); err != nil {
		return nil, err
	}

	r.ttl = ttl
	if err = r.register(); err != nil {
		return nil, err
	}

	r.closeCh = make(chan struct{})

	go r.keepAliveAsync()

	return r, nil
}

// start
func (r *Registry) Start(svr *grpc.Server) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
	for {
		s := <-c
		switch s {
		case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
			svr.Stop()
			r.Stop()
			return
		case syscall.SIGHUP:
		default:
			return
		}
	}
}

// Stop stop register
func (r *Registry) Stop() {
	r.closeCh <- struct{}{}
}

// register 注册节点
func (r *Registry) register() error {
	leaseCtx, cancel := context.WithTimeout(context.Background(), DialTimeout)
	defer cancel()

	leaseResp, err := r.cli.Grant(leaseCtx, r.ttl)
	if err != nil {
		return err
	}
	r.leasesID = leaseResp.ID
	if r.keepAliveCh, err = r.cli.KeepAlive(context.Background(), leaseResp.ID); err != nil {
		return err
	}

	fullKey := r.getFullKey()

	_, err = r.cli.Put(context.Background(), fullKey, r.value, clientv3.WithLease(r.leasesID))
	return err
}

// close 节点
func (r *Registry) close() error {
	fullKey := r.getFullKey()
	_, err := r.cli.Delete(context.Background(), fullKey)
	return err
}

// keepAliveAsync
func (r *Registry) keepAliveAsync() {
	ticker := time.NewTicker(time.Duration(r.ttl) * time.Second)
	for {
		select {
		case <-r.closeCh:
			if err := r.close(); err != nil {
				_ = fmt.Errorf("close failed: %s", err.Error())
			}
			if _, err := r.cli.Revoke(context.Background(), r.leasesID); err != nil {
				_ = fmt.Errorf("revoke failed: %s", err.Error())
			}
			return
		case res := <-r.keepAliveCh:
			if res == nil {
				if err := r.register(); err != nil {
					_ = fmt.Errorf("register failed: %s", err.Error())
				}
			}
		case <-ticker.C:
			if r.keepAliveCh == nil {
				if err := r.register(); err != nil {
					_ = fmt.Errorf("register failed: %s", err.Error())
				}
			}
		}
	}
}

func (r *Registry) GetServerInfo() (string, error) {
	fullKey := r.getFullKey()
	resp, err := r.cli.Get(context.Background(), fullKey)
	if err != nil {
		return "", err
	}

	return string(resp.Kvs[0].Value), nil
}

func (r *Registry) getFullKey() string {
	if r.id > 0 {
		r.fullKey = makeEtcdKey(r.key, r.id)
	} else {
		r.fullKey = makeEtcdKey(r.key, int64(r.leasesID))
	}
	return r.fullKey
}